const COMMON_ORDINAL_DATE: &[usize] = &[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];

const LUNAR_OFFSET: &[usize] = &[
    49, 38, 28, 46, 34, 24, 43, 32, 21, 40, 29, 48, 36, 25, 44, 33, 22, 41, 31, 50, 38, 27, 46, 35,
    23, 43, 32, 22, 40, 29, 47, 36, 25, 44, 34, 23, 41, 30, 49, 38, 26, 45, 35, 24, 43, 32, 21, 40,
    28, 47, 36, 26, 44, 33, 23, 42, 30, 48, 38, 27, 45, 35, 24, 43, 32, 20, 39, 29, 47, 36, 26, 45,
    33, 22, 41, 30, 48, 37, 27, 46, 35, 24, 43, 32, 50, 39, 28, 47, 36, 26, 45, 34, 22, 40, 30, 49,
    37, 27, 46, 35, 23, 42, 31, 21, 39, 28, 48, 37, 25, 44, 33, 22, 40, 30, 49, 38, 27, 46, 35, 24,
    42, 31, 21, 40, 28, 47, 36, 25, 43, 33, 22, 41, 30, 49, 38, 27, 45, 34, 23, 42, 31, 21, 40, 29,
    47, 36, 25, 44, 32, 22, 41, 31, 49, 38, 27, 45, 34, 23, 42, 32, 20, 39, 28, 47, 35, 25, 44, 33,
    22, 41, 30, 49, 37, 26, 45, 35, 23, 42, 32, 21, 39, 28, 47, 36, 25, 44, 33, 23, 40, 29, 48, 37,
    26, 45, 35, 24, 42, 31, 20, 39,
];
/// Calculate the weekday of the given date.
/// Returns a day-of-week number starting from Monday = 0.
///
/// `w`:            | `Mon` | `Tue` | `Wed` | `Thu` | `Fri` | `Sat` | `Sun`
/// --------------- | ----- | ----- | ----- | ----- | ----- | ----- | -----
/// `dow(y, m, d)`: | 0     | 1     | 2     | 3     | 4     | 5     | 6
///
/// Reference: https://en.wikipedia.org/wiki/Zeller%27s_congruence
/// Copied from: https://github.com/zier-one/chinese_holiday_rs/blob/main/src/lib.rs#L80
/// Note: I owned this GitHub account([zier-one](https://github.com/zier-one)) and repo([chinese_holiday_rs]https://github.com/zier-one/chinese_holiday_rs).
#[inline]
fn dow(y: usize, m: usize, d: usize) -> usize {
    if m <= 2 {
        return dow(y - 1, m + 12, d);
    }
    let h = (d + (13 * (m + 1)) / 5) + y + y / 4 - y / 100 + y / 400 + 5;
    h % 7
}

/// Check if the given year is a leap year.
#[inline]
fn is_leap_year(year: usize) -> bool {
    if year < 1600 {
        year % 4 == 0
    } else {
        (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
    }
}

/// Calculate the ordinal date of the given date.
#[inline]
fn doy(year: usize, month: usize, day: usize) -> usize {
    let mut ordinal_date = COMMON_ORDINAL_DATE[month - 1] + day;
    if is_leap_year(year) && month > 2 {
        ordinal_date += 1;
    }
    ordinal_date
}

/// Calculate the weeks per year.
/// Reference: https://en.wikipedia.org/wiki/ISO_week_date
#[inline]
fn wpy(year: usize) -> usize {
    let p = |y: usize| (y + y / 4 - y / 100 + y / 400) % 7;
    if p(year) == 4 || p(year - 1) == 3 {
        53
    } else {
        52
    }
}

/// Calculate the week number of the given date.
/// Reference: https://en.wikipedia.org/wiki/ISO_week_date
#[inline]
fn woy(year: usize, month: usize, day: usize) -> usize {
    let w = (10 + doy(year, month, day) - (dow(year, month, day) + 1)) / 7;
    if w < 1 {
        wpy(year - 1)
    } else if w > wpy(year) {
        1
    } else {
        w
    }
}

#[inline]
fn lunar_new_year_rest(year: usize, month: usize, day: usize) -> usize {
    if doy(year, 1, 1) + LUNAR_OFFSET[year - 1900 - 1] - 1 >= doy(year, month, day) {
        doy(year, 1, 1) + LUNAR_OFFSET[year - 1900 - 1] - 1 - doy(year, month, day)
    } else {
        doy(year, 12, 31) + LUNAR_OFFSET[year - 1900] - doy(year, month, day)
    }
}

pub fn time_info(time: &str) -> String {
    let mut time = time.split("-");
    let year = time.next().unwrap().parse::<usize>().unwrap();
    let month = time.next().unwrap().parse::<usize>().unwrap();
    let day = time.next().unwrap().parse::<usize>().unwrap();
    format!(
        "{},{},{}",
        woy(year, month, day),
        doy(year, 12, 31) - doy(year, month, day),
        lunar_new_year_rest(year, month, day)
    )
}
